
import {
  Array,
  Assignment,
  Binary,
  Break,
  Call,
  Caseclause,
  Catch,
  Closure,
  Continue,
  Defer,
  Descriptor,
  Echo,
  Emit,
  Elif,
  Else,
  Expression,
  External,
  Field,
  For,
  Group,
  Identifier,
  If,
  Ignore,
  Index,
  Iota,
  List,
  Map,
  Member,
  Multivalued,
  Noop,
  Ofclause,
  Out,
  Pair,
  Param,
  Property,
  Range,
  Signature,
  Slice,
  Step,
  Structure,
  Super,
  Switch,
  This,
  Throw,
  Traits,
  Try,
  TypeAssertion,
  Unary,
  Update,
  When,
  Yield,
  YieldClause,
} from '../ast/types'

import sign, {
  isAssign,
  isInfix,
  isKeyword,
  isLiteral,
  isPrefix,
  isSuffix,
  powerOf,
} from '../ast/sign'

import decode from './decode'

const
  IDENT     = sign.ident,
  IOTA      = sign.iota,

  FN        = sign.fn,
  BREAK     = sign.break,
  CASE      = sign.case,
  CATCH     = sign.catch,
  CONTINUE  = sign.continue,
  DEFER     = sign.defer,
  ECHO      = sign.echo,
  ELIF      = sign.elif,
  ELSE      = sign.else,

  FOR       = sign.for,
  IF        = sign.if,
  OUT       = sign.out,
  SWITCH    = sign.switch,
  THROW     = sign.throw,
  TRY       = sign.try,
  YIELD     = sign.yield,
  SUPER     = sign.super,
  THIS      = sign.this,

  OF        = sign.of,
  STRUCT    = sign.struct,
  NULL      = sign.null,

  EQUAL     = sign['='],
  COLON_EQ  = sign[':='],
  COLON     = sign[':'],
  COMMA     = sign[','],
  DOT       = sign['.'],
  DOTDOT    = sign['..'],
  IGNORE    = sign['_'],
  MINUS     = sign['-'],
  PLUS      = sign['+'],

  IS        = sign.is,
  NOT       = sign.not,

  PLUSx     = sign['+x'],
  MINUSx    = sign['-x'],

  LPARENS   = sign['('],
  RPARENS   = sign[')'],
  LBRACES   = sign['{'],
  RBRACES   = sign['}'],
  LBRACKETS = sign['['],
  RBRACKETS = sign[']'],

  PUPDATE   = powerOf(sign['--'])

function beMap (s) {
  let
    node = new Map().start(s.start),
    sign = s.beMore()

  node.append(beType(s, sign))

  sign = s.beMore()
  while (sign !== RBRACES) {
    if (sign === COMMA) sign = s.beMore()
    node.append(beType(s, sign))
    sign = s.beMore()
  }

  return node.end(s.end)
}

function beSliceOrArray (s) {
  let
    start = s.start,
    mold = beType(s, s.beMore()),
    sign = s.beMore(),
    node
      = sign === RBRACKETS
      && new Slice(mold, null).start(start).end(s.end)
      || soCapacity(s, mold, start,
        sign === COMMA && s.beMore() || sign
      )

  return node
}

function soCapacity (s, mold, start, sign) {
  let
    [x, last] = beExpression(
      s, sign === PLUS && s.beMore() || sign
    ),
    node
      = last !== RBRACKETS && s.fail(last)
      || sign === PLUS
      && new Slice(mold, x)
      || new Array(mold, x)

  return node.start(start).end(s.end)
}

function beStructure (s) {
  let
    node = new Structure().start(s.start),
    sign = s.beMore()

  if (sign !== LBRACKETS) s.fail(sign)

  node.append(beField(s, s.beMore()))
  sign = s.beMore()
  while (sign !== RBRACKETS) {
    let
      field = beField(
        s, sign === COMMA && s.beMore() || sign
      ),
      name = field.id && field.id.name || ''

    if (name && node.items.some(x => x.id && x.id.normalize() === name))
      s.exists('the field', field.id)

    node.append(field)
    sign = s.beMore()
  }

  return node.end(s.end)
}

function soUnary (s, operator) {
  let start = s.start, sign = s.beRight()

  if (sign === LBRACES || sign === LBRACKETS)
    s.fail(sign)

  return new Unary(operator, beFactor(s, sign))
    .start(start).end(s.end)
}

function beDescriptor (s, left, sign) {
  let
    stop = sign === LBRACES
      && RBRACES
      || sign === LBRACKETS
      && RBRACKETS
      || s.fail(sign),
    node = new Descriptor(left, sign === LBRACES && '{}' || '[]')
      .start(left.loc.start)


  sign = s.beMore()
  while (sign !== stop) {
    let x = null, v = null

    if (isKeyword(sign)) {
      x = Id(s)
      sign = s.beMore()
      if (sign !== COLON) s.fail(sign)
      sign = s.beMore()
    }

    [v, sign] = soDescriptor(s, sign)

    if (sign === COLON)
    {
      if (x) s.fail(sign)
      x = v;
      [v, sign] = soDescriptor(s, s.beMore())
    }

    if (x)
      v = new Property(x, v)
        .start(x.loc.start).end(v.loc.end)

    node.append(v)

    if (sign === COMMA) sign = s.beMore()
  }

  return node.end(s.end)
}

function beParam (s, isout, sign, params) {
  let
    first = sign,
    start = s.start,
    mold  = beType(s, sign),
    owner =
      s.rune === 0x21 // !
      && (
        !isout && s.drop() && true
        || s.unexpected()
      )
      || false

  sign = s.beMore()

  params.push(
    new Param(
      mold, owner, sign ===  IDENT && Id(s)
      || first === IDENT && mold || null
    )
      .start(start).end(s.end)
  )

  if (sign === IDENT) sign = s.beMore()

  return sign === COMMA && s.beMore() || sign
}

function beDefer (s) {
  let
    node = new Defer().start(s.start),
    sign = s.soRight()

  if (sign === LBRACES)
    return soStatements(s, node, sign)

  if (sign === 0)
    return soStatements(s, node, s.beMore())

  let x = beStatement(s, sign)
  node.append(x)
  return node.end(x.loc.end)
}

function soYieldClause (s, action) {
  // for x  --> Expression
  // for x, --> soYieldClause
  // action for ...
  let
    line = s.line,
    [x, sign] = beExpression(s, s.beRight())

  if (sign === LBRACES || s.line !== line) {
    if (sign && action) s.fail(sign)
    return [x, sign]
  }

  let
    start = x.loc.start,
    results = [], clasue = null, size = null

  while (sign !== OF) {
    results.push(x)
    if (sign === COMMA) sign = s.beRight();
    [x, sign] = beExpression(s, sign)
  }
  results.push(x);

  [clasue, sign] = beExpression(s, s.beRight())

  if (sign === DOTDOT)
  {
    [size, sign] = beExpression(s, s.beRight())
    clasue = new Range(clasue, size)
      .start(clasue.loc.start).end(size.loc.end)
    size = null
  }
  else
    s.assert(clasue, Call)

  if (sign && sign !== LBRACES && line === s.line)
    [size, sign] = beExpression(
      s, sign === COMMA && s.beRight() || sign
    )

  return [
    new YieldClause(results, clasue, size)
      .start(start).end((size || clasue).loc.end),
    sign
  ]
}

function soWhen (s, action, sign) {
  if (!sign) return action
  if (sign === FOR) return beFor(s, action)
  if (sign === IF) return beIf(s, If, action)
  s.fail(sign)
}

function beFor (s, action) {
  let
    start = s.start,
    [x, sign] = soYieldClause(s, action)

  if (action) {
    x = new When(action, new For(x).start(start).end(x.loc.end))
      .start(action.loc.start).end(x.loc.end)

    return soWhen(s, x, sign)
  }

  return soStatements(s, new For(x).start(start), sign)
}

function beIf (s, type, action) {
  let
    line = s.line,
    start = s.start, y = null, z,
    [x, sign] = beExpression(s, s.beRight())

  if (sign === COMMA)
  {
    [y, sign] = beExpression(s, s.beRight())
    if (sign !== OF || s.line !== line) s.fail(sign);
    [z, sign] = beExpression(s, s.beRight())
    x = new TypeAssertion(x, y, z)
      .start(x.loc.start).end(z.loc.end)
  }
  else if (sign === OF)
  {
    if (s.line !== line) s.fail(sign);
    [z, sign] = beExpression(s, s.beRight())
    x = new TypeAssertion(x, null, z)
      .start(x.loc.start).end(z.loc.end)
  }

  if (action) {
    x = new When(
      action, new type(x).start(start).end(x.loc.end)
    ).start(action.loc.start).end(x.loc.end)

    return soWhen(s, x, sign)
  }

  if (!sign || s.line === line && sign !== LBRACES)
    s.fail(sign)

  x = soStatements(s, new type(x).start(start), sign)

  return x
}

function beSwitch (s) {
  let
    start = s.start,
    [x, sign] = soYieldClause(s)

  return soStatements(s, new Switch(x).start(start), sign)
}

function beCase (s) {
  let
    start = s.start,
    [x, sign] = soYieldClause(s)

  return soStatements(s, new Caseclause(x).start(start), sign)
}

function beOfclause (s) {
  // if x, instance of type --> TypeAssertion
  // of type --> beOfclause
  let
    start = s.start,
    x = beType(s, s.beRight)

  return soStatements(
    s, new Ofclause(x).start(start), s.beMore()
  )
}

function beElse (s) {
  let
    start = s.start,
    sign = s.soRight(),
    node = sign !== IF
      && new Else().start(start)
      || null

  if (!node) return beIf(s, Elif)

  if (sign === LBRACES) return soStatements(s, node, sign)
  if (!sign) return soStatements(s, node, s.beMore())

  let x = beStatement(s, sign, 0)
  node.append(x)
  return node.end(x.loc.end)
}

function beTry (s) {
  let
    sign = s.beMore(),
    node = soStatements(
      s, new Try().start(s.start), sign
    )

  sign = s.beMore()

  s.beAlign()

  if (sign !== CATCH) s.fail(sign)

  let x = beCatch(s)
  node.append(x)
  return node.end(x.loc.end)
}

function beCatch (s) {
  let
    start = s.start,
    sign = s.beRight(),
    node = new Catch(
      sign === IGNORE && Ig(s)
      || sign === IDENT && Id(s)
      || s.fail(sign)
    ).start(start)

  if (sign === IDENT)
    return soStatements(s, node, s.beMore())

  s.beZero(s.soMore())
  return node.end(s.end)
}

function beEcho (s) {
  let
    node = new Echo().start(s.start),
    [x, sign] = soDescriptor(s, s.beMore())

  node.append(x)

  while (sign) {
    if (sign === IF) return beIf(
      s, If, node.end(x.loc.end)
    )
    if (sign === FOR) return beFor(
      s, node.end(x.loc.end)
    )

    if (sign === COMMA) sign = s.beMore();
    [x, sign] = soDescriptor(s, sign)
    node.append(x)
  }

  return node.end(x.loc.end)
}

function beThrow (s) {
  let
    start = s.start,
    [clasue, sign] = beExpression(s, s.beMore()),
    msg = null, code = null, content = null

  if (!sign) s.fail(sign)
  if (sign === COMMA) sign = s.beMore();
  [msg, sign] = beExpression(s, sign)

  if (sign === COMMA) sign = s.beMore()
  if (sign) [code, sign] = beExpression(s, sign)

  if (sign === COMMA) sign = s.beMore()
  if (sign) [content, sign] = soDescriptor(s, sign)

  s.beZero(sign)
  return new Throw(clasue, msg, code, content)
    .start(start).end((content || code || msg).loc.end)
}

function beOut (s, classes) { // yield
  let
    node = new classes().start(s.start).end(s.end),
    sign = s.soMore(), x

  while (sign) {
    if (sign === IF) return beIf(
      s, If, node.end(x && x.loc.end || node.loc.end)
    )

    if (sign === FOR) {
      if (classes === Out) s.fail(sign)
      return beFor(
        s, node.end(x && x.loc.end || node.loc.end)
      )
    }

    [x, sign] = soDescriptor(s, sign)

    node.append(x)

    s.assert(x, Expression)

    if (sign === COMMA) sign = s.beMore()
  }

  return x && node.end(x.loc.end) || node
}

function soDescriptor (s, sign, x) {
  [x, sign] = beExpression(s, sign)
  if (sign === LBRACES) {
    x = beDescriptor(s, x, sign)
    sign = s.soMore()
  }
  return [x, sign]
}

function beBreak (s, classes) { // continue
  let
    node = new classes().start(s.start).end(s.end),
    sign = s.soMore()

  if (sign === IF) return beIf(s, If, node)

  s.beZero(sign)
  return node
}

function beStep (s, left, sign) {
  let
    x = getHighest(s, left, PUPDATE),
    node = new Step(x, sign)
      .start(x.loc.start).end(s.end)
  return endHighest(left, x, node)
}

function beClosure (s) {
  let
    start  = s.start,
    node = new Closure(s.sps().beId(), beSignature(s))
      .start(start)

  return soStatements(s, node, s.beMore())
}

function beList (s, x) { // mold
  let
    sign = s.beMore(),
    node = new List(x)
      .start(x.loc.start),
    v = x instanceof Map && x || null

  if (sign !== LBRACKETS) s.fail(sign)

  sign = s.beMore()

  while (sign !== RBRACKETS) {
    [x, sign] = soDescriptor(s, sign)

    if (v)
    {
      if (sign !== COLON) s.fail(sign);
      [v, sign] = soDescriptor(s, s.beRight())
      x = new Pair(x, v)
        .start(x.loc.start).end(v.loc.end)
    }
    node.append(x)
  }

  return node.end(s.end)
}

function getHighest (s, left, rbp) {
  while (left.rbp() < rbp) {
    left = left.right
    if (!left) s.fail('illegal operator')
  }
  return left
}

function endHighest (left, highest, node) {
  if (left === highest) return node
  let
    end = node.loc.end,
    lowest = left.end(end)

  while (lowest.right !== highest) {
    lowest = lowest.right
    lowest.end(end)
  }

  lowest.right = node

  return left
}

function beUpdate (s, left, sign) {
  let
    x = getHighest(s, left, PUPDATE),
    node = new Update(x, sign)
      .start(x.loc.start).end(s.end)

  s.assert(
    x, Identifier, This, Super, Member, Call, Group
  )
  return endHighest(left, x, node)
}

function beBinary (s, left, sign) {
  let
    x = getHighest(s, left, powerOf(sign)),
    r = s.beMore(),
    y = (sign === IS || sign === NOT)
      && (
        r === LBRACES && beMap(s)
        || r === LBRACKETS && beSliceOrArray(s)
        || sign === FN && beSignature(s)
      )
      || beFactor(s, r),

    node = new Binary(x, sign, y)
      .start(x.loc.start).end(y.loc.end)

  return endHighest(left, x, node)
}

function beFactor (s, sign) {

  if (sign === IDENT)
    return soChain(s, Id(s))

  if (sign === THIS)
    return toTarget(
      s.nothis && s.fail(sign) || s
      , new This().start(s.start).end(s.end)
      , Call
    )

  if (sign === SUPER)
    return toTarget(
      s.nosuper && s.fail(sign) || s
      , new Super().start(s.start).end(s.end)
      , Call
    )

  if (isLiteral(sign))
    return decode(s, sign)

  if (sign === LPARENS) {
    let x, start = s.start
    sign = s.beMore();
    [x, sign] = beExpression(
      s, sign === LBRACKETS && s.fail(sign)
      || sign === LBRACES && s.fail(sign)
      || sign
    )

    if (sign !== RPARENS) s.fail(sign)

    while (x instanceof Group) x = x.expression

    return new Group(x).start(start).end(s.end)
  }

  if (isPrefix(sign))
    return soUnary(s, sign)

  if (sign === PLUS)
    return soUnary(s, PLUSx)

  if (sign === MINUS)
    return soUnary(s, MINUSx)

  if (sign === IOTA && s.iotable)
    return new Iota()
      .start(s.start).end(s.end)

  s.fail(sign)
}

function soChain (s, x) {

  while (true) {
    let sign = s.soChain()

    if (!sign) break

    if (sign === DOT)
      x = new Member(x, s.asId())
        .start(x.loc.start).end(s.end)

    else if (sign === LPARENS)
      x = beArguments(
        s,
        new Call(x).start(x.loc.start)
      )

    else if (sign === LBRACKETS)
      x = beDescriptor(s, x, sign)

    else
      s.fail(sign)
  }

  return x
}

function soTarget (s, sign) {
  return toTarget(
    s,
    sign === IDENT
      && Id(s)
      || sign === SUPER
      && new Super().start(s.start).end(s.end)
      || sign === THIS
      && new This().start(s.start).end(s.end)
      || null
  )
}

function toTarget (s, x, type) {
  while (x) {
    let sign = s.soChain()
    if (!sign) break

    if (sign === DOT)
      x = new Member(x, s.asId())
        .start(x.loc.start).end(s.end)

    else if (sign === LBRACKETS)
    {
      let [y, next] = beExpression(s, s.beMore())
      if (next !== RBRACKETS) s.fail(next)
      x = new Index(x, y)
        .start(x.loc.start).end(s.end)
    }
    else if (sign === LPARENS)

      return beArguments(
        type && s || s.fail(sign),
        new type(x).start(x.loc.start)
      )
    else
      s.fail(sign)
  }

  return x
}

function beMultivalued (s, x) {
  let
    sign = s.beMore(),
    node = new Multivalued().start(x.loc.start)

  node.targets.push(x)

  while (sign) {
    x = sign === IGNORE && Ig(s) || soTarget(s, sign)

    if (!x) s.fail(sign)
    sign = s.beMore()
    node.targets.push(x)

    if (sign === LPARENS) s.fail(sign)
    if (sign === EQUAL || sign === COLON_EQ)
      break

    if (sign === COMMA) sign = s.beMore()
  }

  node.operator = sign

  sign = s.beMore()
  while (true) {
    [x, sign] = soList(s, sign)
    node.append(x)

    if (sign === COMMA)
      sign = s.beMore()
    else if (!sign)
      return node.end(x.loc.end)
    else if (sign === IF || sign === FOR)
      break
  }

  return soWhen(s, node.end(x.loc.end), sign)
}

function beAssignment (s, left, operator) {
  let [x, sign] = soList(s, s.beMore())
  return soWhen(s, toAssignment(s, left, operator, x), sign)
}

function beArguments (s, node) {
  let x, sign = s.beMore()

  while (sign !== RPARENS) {
    [x, sign] = soDescriptor(s, sign)

    node.append(x)

    if (sign === COMMA) sign = s.beMore()
  }

  return node.end(s.end)
}

function beStmt (s, left) {
  let
    x = toTarget(s, left, Emit),
    sign = s.soMore()

  if (x instanceof Emit)
    return sign && s.fail(sign) || x

  if (sign === COMMA)
    return beMultivalued(s, x)

  if (isAssign(sign))
    return beAssignment(s, x, sign)

  if (isSuffix(sign))
    return soWhen(s, beStep(s, x, sign), s.soMore())

  s.fail(sign)
}

function beStatement (s, sign, prev) {
  switch (sign) {
    case IDENT:
      return beStmt(s, Id(s))
    case SUPER:
      return beStmt(
        s,
        new Super().start(s.start).end(s.end)
      )
    case THIS:
      return beStmt(
        s,
        new This().start(s.start).end(s.end)
      )
    case IGNORE: {
      let x = Ig(s)
      sign = s.beMore()
      if (sign === COMMA) return beMultivalued(s, x)
      if (!isAssign(sign)) s.fail(sign)
      return beAssignment(s, x, sign)
    }

    case ELIF:
      if (
        prev !== IF && prev !== ELSE && prev !== ELIF && prev !== OF
      )
        s.fail(sign)
      return beIf(s, Elif)

    case ELSE:
      if (
        prev !== IF && prev !== ELSE && prev !== ELIF && prev !== OF
        && prev !== CASE && prev !== SWITCH
      )
        s.fail(sign)
      return beElse(s)

    case FOR:
      return beFor(s)

    case IF:
      return beIf(s, If)

    case OF:
      if (prev !== IF && prev !== ELSE && prev !== OF)
        s.fail(sign)
      return beOfclause(s)

    case BREAK:
      return beBreak(s, Break)

    case CONTINUE:
      return beBreak(s, Continue)

    case CATCH:
      return beCatch(s)

    case DEFER:
      return beDefer(s)

    case ECHO:
      return beEcho(s)

    case THROW:
      return beThrow(s)

    case TRY:
      return beTry(s)

    case YIELD:
      return beOut(s, Yield)

    case OUT:
      return beOut(s, Out)

    case SWITCH:
      return beSwitch(s)

    case CASE:
      if (prev !== CASE && prev !== SWITCH)
        s.fail(sign)
      return beCase(s)

    case FN:
      return beClosure(s)

    case NULL:
      return soWhen(s, new Noop().start(s.start).end(s.end), s.soMore())

    default:
      s.fail(sign)
  }

}

export function soStatements (s, parent, sign) {
  let
    prev = 0,
    stop = sign === LBRACES && s.left || 0,
    x
  if (stop)
    sign = s.beNext()
  else
    s.beIndent()

  s.lead()
  while (sign) {
    x = beStatement(s, sign, prev)
    parent.append(x)
    if (s.column !== s.left) break
    prev = sign
    sign = s.right() || s.unexpected()
  }

  s.beZero(s.soMore())

  if (!stop)
    s.left = s.left && s.column || 0
  else if (s.column !== stop || !s.rightBraces())
    s.unexpected()
  else {
    s.left = stop
    s.cwsp()
  }

  return parent.end(x.loc.end)
}

export function toAssignment (s, target, operator, source) {
  return new Assignment(target, operator, source)
    .start(target.loc.start).end(source.loc.end)
}

export function soList (s, sign) {
  if (sign === LBRACKETS)
    return [
      beList(s, beSliceOrArray(s)), s.soMore()
    ]

  if (sign === LBRACES)
    return [
      beDescriptor(s, beMap(s), s.beMore()), s.soMore()
    ]

  if (sign === STRUCT)
    return [
      beDescriptor(s, beStructure(s), s.beMore()), s.soMore()
    ]

  return beExpression(s, sign)
}

export function beExpression (s, sign) {
  return soExpression(s, beFactor(s, sign))
}

function soExpression (s, left) {
  let sign
  while (true) {
    sign = s.soMore()
    if (isInfix(sign))
      left = beBinary(s, left, sign)
    else if (isSuffix(sign))
      left = beUpdate(s, left, sign)
    else
      break
  }

  return [left, sign]
}

export function soExternal (s, id) {
  return s.dot()
    && new External(id, s.asId())
      .start(id.loc.start).end(s.end)
    || id
}

export function beType (s, sign) {

  switch (sign) {
    case IDENT:
      return soExternal(s, Id(s))

    case LBRACES:
      return beMap(s)

    case LBRACKETS:
      return beSliceOrArray(s)

    case FN:
      return beSignature(s)

    case THIS:
      return new This()
        .start(s.start).end(s.end)

    case SUPER:
      return new Super()
        .start(s.start).end(s.end)

    case STRUCT:
      return beStructure(s)
  }

  s.fail(sign)
}

export function beSignature (s) {

  let
    sign = s.beMore(),
    stop = sign === LBRACKETS && RBRACKETS
      || sign === LPARENS && RPARENS
      || s.fail(sign),
    start = s.start,
    params = [],
    results = [],
    yieldable = false


  sign = s.beMore()

  while (sign !== stop) {
    if (sign === OUT) {
      sign = s.beMore()
      if (sign === stop) s.fail(sign)
      while (sign !== stop)
        sign = beParam(s, true, sign, results)
      break
    }
    if (sign === YIELD) {
      yieldable = true
      sign = s.beMore()
      if (sign === stop) s.fail(sign)
      while (sign !== stop)
        sign = beParam(s, true, sign, results)
      break
    }
    sign = beParam(s, false, sign, params)
  }

  return new Signature(params, results, yieldable)
    .start(start).end(s.end)
}

export function beTraits (s) {
  let node = new Traits().start(s.start)

  while (true) {
    let
      x = soExternal(s, s.beId()),
      name = x.normalize(),
      sign = s.soRight()

    if (node.factor(t => t.normalize() === name))
      s.exists('traits', x)

    node.append(x)

    if (!sign) return node.end(x.loc.end)

    if (sign !== COMMA) s.fail(sign)
  }
}

export function beField (s, sign) {
  let
    start = s.start,
    id = sign === IDENT && Id(s)
      || isKeyword(sign) && Id(s)
      || null,
    ignore = sign === IGNORE && s.drop() && true
      || id && s.lowline()
      || false,
    nullable = !id && !ignore && s.fail(sign)
      || s.questionMark(),
    mold = beType(s, s.beRight())

  return new Field(id, ignore, nullable, mold)
    .start(start).end(s.end)
}

export function accessify (text) {
  return text.substring(0, text.indexOf(' '))
}

export function Id (s) {
  return new Identifier(s.text())
    .start(s.start).end(s.end)
}

export function Ig (s) {
  return new Ignore()
    .start(s.start).end(s.end)
}
